package com.cloudream.utils;/*
 * BeDecoder.java
 *
 * Created on May 30, 2003, 2:44 PM
 * Copyright (C) 2003, 2004, 2005, 2006 Aelitis, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * AELITIS, SAS au capital de 46,603.30 euros
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 */

import net.sf.json.JSONArray;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.log4j.Logger;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.*;

/**
 * A set of utility methods to decode a bencoded array of byte into a Map.
 * integer are represented as Long, String as byte[], dictionnaries as Map, and
 * list as List.
 *
 * @author TdC_VgA
 */
@SuppressWarnings(value = {"rawtypes", "unchecked", "unused"})
public class BDecoder {
    private static Logger logger = Logger.getLogger(BDecoder.class);

    public static Charset BYTE_CHARSET = Charset.forName("UTF-8");
    ;

    public static Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    ;

    private boolean recovery_mode;


    public static Map decode(byte[] data) throws IOException {
        return (new BDecoder().decodeByteArray(data));
    }

    public static Map decode(BufferedInputStream is) throws IOException {
        return (new BDecoder().decodeStream(is));
    }

    public BDecoder() { }

    public Map decodeByteArray(byte[] data) throws IOException {
        return (decode(new ByteArrayInputStream(data)));
    }

    public Map decodeStream(BufferedInputStream data) throws IOException {
        Object res = decodeInputStream(data, 0);

        if (res == null) {
            throw (new IOException("BDecoder: zero length file"));
        } else if (!(res instanceof Map)) {
            throw (new IOException("BDecoder: top level isn't a Map"));
        }

        return ((Map) res);
    }

    private Map decode(ByteArrayInputStream data) throws IOException {
        Object res = decodeInputStream(data, 0);

        if (res == null) {
            throw (new IOException("BDecoder: zero length file"));
        } else if (!(res instanceof Map)) {
            throw (new IOException("BDecoder: top level isn't a Map"));
        }

        return ((Map) res);
    }

    private Object decodeInputStream(InputStream bais, int nesting)

            throws IOException {
        if (nesting == 0 && !bais.markSupported()) {
            throw new IOException("InputStream must support the mark() method");
        }
        // set a mark
        bais.mark(Integer.MAX_VALUE);
        // read a byte
        int tempByte = bais.read();
        // decide what to do
        switch (tempByte) {
            case 'd':
                // create a new dictionary object
                Map tempMap = new HashMap();
                try {
                    // get the key
                    byte[] tempByteArray = null;
                    while ((tempByteArray =
                            (byte[]) decodeInputStream(bais, nesting + 1)) != null) {
                        // decode some more
                        Object value = decodeInputStream(bais, nesting + 1);
                        // add the value to the map
                        CharBuffer cb =
                                BYTE_CHARSET.decode(ByteBuffer.wrap(tempByteArray));
                        String key = new String(cb.array(), 0, cb.limit());
                        tempMap.put(key, value);
                    }
                    bais.mark(Integer.MAX_VALUE);
                    tempByte = bais.read();
                    bais.reset();
                    if (nesting > 0 && tempByte == -1) {
                        throw (new IOException(
                                "BDecoder: invalid input data, 'e' missing from end of dictionary"));
                    }
                } catch (Throwable e) {
                    if (!recovery_mode) {
                        if (e instanceof IOException) {
                            throw ((IOException) e);
                        }
                        throw (new IOException(e.toString()));
                    }
                }
                // return the map
                return tempMap;
            case 'l':
                // create the list
                List tempList = new ArrayList();
                try {
                    // create the key
                    Object tempElement = null;
                    while ((tempElement = decodeInputStream(bais, nesting + 1)) != null) {
                        // add the element
                        tempList.add(tempElement);
                    }
                    bais.mark(Integer.MAX_VALUE);
                    tempByte = bais.read();
                    bais.reset();
                    if (nesting > 0 && tempByte == -1) {
                        throw (new IOException(
                                "BDecoder: invalid input data, 'e' missing from end of list"));
                    }
                } catch (Throwable e) {
                    if (!recovery_mode) {
                        if (e instanceof IOException) {
                            throw ((IOException) e);
                        }
                        throw (new IOException(e.toString()));
                    }
                }
                // return the list
                return tempList;

            case 'e':
            case -1:
                return null;
            case 'i':
                return new Long(getNumberFromStream(bais, 'e'));
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                // move back one
                bais.reset();
                // get the string
                return getByteArrayFromStream(bais);
            default: {
                int rem_len = bais.available();
                if (rem_len > 256) {
                    rem_len = 256;
                }
                byte[] rem_data = new byte[rem_len];
                bais.read(rem_data);
                throw (new IOException("BDecoder: unknown command '" + tempByte
                        + ", remainder = " + new String(rem_data, DEFAULT_CHARSET)));
            }
        }
    }

    private long getNumberFromStream(
            InputStream bais, char parseChar) throws IOException {
        StringBuffer sb = new StringBuffer(3);

        int tempByte = bais.read();
        while ((tempByte != parseChar) && (tempByte >= 0)) {
            sb.append((char) tempByte);
            tempByte = bais.read();
        }

        // are we at the end of the stream?
        if (tempByte < 0) {
            return -1;
        }

        return Long.parseLong(sb.toString());
    }

    // This one causes lots of "Query Information" calls to the filesystem
    private long getNumberFromStreamOld(
            InputStream bais, char parseChar) throws IOException {
        int length = 0;
        // place a mark
        bais.mark(Integer.MAX_VALUE);
        int tempByte = bais.read();
        while ((tempByte != parseChar) && (tempByte >= 0)) {
            tempByte = bais.read();
            length++;
        }

        // are we at the end of the stream?
        if (tempByte < 0) {
            return -1;
        }

        // reset the mark
        bais.reset();
        // get the length
        byte[] tempArray = new byte[length];
        int count = 0;
        int len = 0;

        // get the string
        while (count != length
                && (len = bais.read(tempArray, count, length - count)) > 0) {
            count += len;
        }

        // jump ahead in the stream to compensate for the :
        bais.skip(1);
        // return the value
        CharBuffer cb =
                DEFAULT_CHARSET.decode(ByteBuffer.wrap(tempArray));
        String str_value = new String(cb.array(), 0, cb.limit());

        return Long.parseLong(str_value);
    }

    private byte[] getByteArrayFromStream(
            InputStream bais) throws IOException {
        int length = (int) getNumberFromStream(bais, ':');
        if (length < 0) {
            return null;
        }
        // note that torrent hashes can be big (consider a 55GB file with 2MB
        // pieces
        // this generates a pieces hash of 1/2 meg

        if (length > 8 * 1024 * 1024) {
            throw new IOException("Byte array length too large (" + length + ")");
        }

        byte[] tempArray = new byte[length];
        int count = 0;
        int len = 0;
        // get the string
        while (count != length
                && (len = bais.read(tempArray, count, length - count)) > 0) {
            count += len;
        }
        if (count != tempArray.length) {
            throw (new IOException(
                    "BDecoder::getByteArrayFromStream: truncated"));
        }
        return tempArray;
    }

    public void setRecoveryMode(boolean r) {
        recovery_mode = r;
    }

    private void print(PrintWriter writer, Object obj) {
        print(writer, obj, "", false);
    }

    private void print(
            PrintWriter writer, Object obj, String indent, boolean skip_indent) {
        String use_indent = skip_indent ? "" : indent;
        if (obj instanceof Long) {
            writer.println(use_indent + obj);
        } else if (obj instanceof byte[]) {
            byte[] b = (byte[]) obj;
            if (b.length == 20) {
                writer.println(use_indent + " { " + b + " }");
            } else if (b.length < 64) {
                writer.println(new String(b, DEFAULT_CHARSET));
            } else {
                writer.println("[byte array length " + b.length);
            }

        } else if (obj instanceof String) {
            writer.println(use_indent + obj);
        } else if (obj instanceof List) {
            List l = (List) obj;
            writer.println(use_indent + "[");

            for (int i = 0; i < l.size(); i++) {
                writer.print(indent + "  (" + i + ") ");
                print(writer, l.get(i), indent + "    ", true);
            }
            writer.println(indent + "]");

        } else {
            Map m = (Map) obj;
            Iterator it = m.keySet().iterator();

            while (it.hasNext()) {
                String key = (String) it.next();
                if (key.length() > 256) {
                    writer.print(indent + key.substring(0, 256) + "... = ");
                } else {
                    writer.print(indent + key + " = ");
                }
                print(writer, m.get(key), indent + "  ", true);
            }
        }
    }

    private static void print(File f, File output) {
        try {
            BDecoder decoder = new BDecoder();
            decoder.setRecoveryMode(false);
            PrintWriter pw = new PrintWriter(new FileWriter(output));
            decoder.print(pw,
                    decoder.decodeStream(
                            new BufferedInputStream(new FileInputStream(f))));
            pw.flush();
        } catch (Throwable e) {
        }
    }

    public static String getName(File f) {
        try {
            BDecoder decoder = new BDecoder();
            decoder.setRecoveryMode(false);
            Map map = decoder.decodeStream(new BufferedInputStream(new FileInputStream(f)));
            HashMap infoMap = (HashMap) map.get("info");
            List<HashMap> fileList = (List) infoMap.get("files");
//            String c = JSONObject.fromObject(fileList).toString();
//            String c  = JSONArray.fromObject(fileList).toString();
            String c = "";
            for (Map fileMap : fileList) {
                List pathList = (List) fileMap.get("path");
                String path = new String((byte[]) pathList.get(0), "utf-8");
                fileMap.put("path", path);
                int a = 1;
            }
            String c1 = JSONArray.fromObject(fileList).toString();
            int a = 1;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    public static Map httpGetPrint(String[] urls) {
        for (String url : urls) {
            Map map = httpGetPrint(url);
            if(map==null)continue;
            return map;
        }
        return null;
    }

    public static Map httpGetPrint(String url) {
        return httpGetPrint(url, 0);
    }

    public static Map httpGetPrint(String url, int reCnt) {
        if (reCnt > 5) {
            logger.error("失败的链接："+url);
            return null;
        }
        try {
            CloseableHttpClient client = HttpClients.createDefault();;
            //发送get请求
            HttpGet request = new HttpGet(url);
            HttpResponse response = client.execute(request);

            /**请求发送成功，并得到响应**/
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                /**读取服务器返回过来的json字符串数据**/
                InputStream inputStream = response.getEntity().getContent();

                BDecoder decoder = new BDecoder();
                decoder.setRecoveryMode(false);
                Map map = decoder.decodeStream(new BufferedInputStream(inputStream));
                return map;
//                HashMap infoMap = (HashMap) map.get("info");
//                String name = new String((byte[]) infoMap.get("name"));
//                List<HashMap> fileList  = (List) infoMap.get("files");

            } else {
                logger.error("httpGetPrint请求提交失败:" + url);
                httpGetPrint(url, reCnt + 1);
            }
        } catch (Exception ex) {
            logger.error("httpGetPrint请求处理异常:" + ex);
        }
        return null;
    }

    public static void main(String[] args) throws IOException {
        String url = "http://itorrents.org/torrent/3720C4F7239BEE955B8813C39852640D8F9B5C8B.torrent";
        String url1 = "http://magnet.vuze.com/magnetLookup?hash=3464631520FC83214865D7A981875D1305093914879";
        String url2 = "http://bt.box.n0808.com/34/79/3464631520FC83214865D7A981875D1305093914879.torrent";
//        httpGetPrint(url);
        t1(url,"3720C4F7239BEE955B8813C39852640D8F9B5C8B");
//        httpGetPrint(new String[]{url,url1,url2});
//		print(new File(
//				"C:\\Temp\\8565658FA6C187A602A5360A69F11933624DD9B5.dat.bak"),
//				new File("C:\\Temp\\bdecoder.log"));
//        print(new File("d:/2.torrent"), new File("d:/2.properties"));
//        getName(new File("d:/2.torrent"));
    }

    public static boolean t1(String url, String magnet) {
        try (
                OutputStream out = new FileOutputStream("D://"+magnet+".torrent");
        ) {
            CloseableHttpClient client = HttpClients.createDefault(); ;
            //发送get请求
            HttpGet request = new HttpGet(url);
            HttpResponse response = client.execute(request);

            /**请求发送成功，并得到响应**/
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                /**读取服务器返回过来的json字符串数据**/
                InputStream inputStream = response.getEntity().getContent();
                ByteArrayOutputStream swapStream = new ByteArrayOutputStream();

                int temp = 0;
                // 开始拷贝
                while ((temp = inputStream.read()) != -1) {
                    // 边读边写
                    out.write(temp);
                }
                // 关闭输入输出流
                inputStream.close();
                return true;
            } else {
                logger.error("httpGetPrint请求提交失败:" + url);
            }
        } catch (Exception ex) {
            logger.error("httpGetPrint请求处理异常:" + ex);
        }

        return false;
    }
}
